package com.wawayaya.bookreader.stroke;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Join;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.wawayaya.bookreader.bean.ShortPoint;
import com.wawayaya.bookreader.bean.StrokeRecord;
import com.wawayaya.bookreader.bean.WeightPoint;
import com.wawayaya.bookreader.jni.Brush;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

/** view to show stroke feature;
 * caller should implement interface StrokeView.QueryStroke
 * 
 * @author wankang
 */
public class StrokeView extends View
{
	static private final String TAG = StrokeView.class.getSimpleName();
	static private final float EPSILON = 1.0e-5f;
	static private final float PI = 3.14159265f;
	/** for speed control
	 */
	private int N = 5;
	
	private Rect mPadding = new Rect(0, 0, 0, 0);
	private int m_iMargin = 10;
	private int m_iWidth = 0;
	private int m_iHeight = 0;
	private String m_sText = null;
	private int m_iLineColor = 0xffff0000;
	private int m_iWriteColor = 0xff0000ff;
	private int m_iBackColor = 0xffffffff;
	private int m_iStrokeColor = Color.DKGRAY;
	private float m_iWriteWidth = 4;
	
	private Handler m_hHandler = null;
	private Timer m_mTimer = null;
	private TimerTask m_mTimerTask = null;
	
    /** flag of invoking filling
     */
    private int mFillStroke = 0;
    private boolean mStopFill = false;
    /** seed-filling agent
     */
    private FillStroke mFillAgent = null;
    /** 0: block 9 fill; 1: circle fill style
     */
    private int mFillStyle = 0;
    /** every time, maximum fill 40 points
     */
    private int mFillCount = 40;
    /** color in filling, for BuShou
     */
    private int mFillBuShouColor = Color.rgb(0, 142, 225);
    /** color in filling, for BuYu
     */
    private int mFillBuYuColor = Color.rgb(0, 51, 102);
    /** wait time after contour filling end, unit: 100 millisecond
     */
    private int mFillWaitTime = 10;
    /** to invoke animation or not
     */
    private boolean mNeedStartAnimation = false;
    /** rectangle being updated in animation
     */
    private Rect mAnimateRect = null;
	
	/** for normal movement
	 */
	protected static final int MSG_MOVE = 1;
	
    /** points of user drawing
     */
	private Vector<ShortPoint> m_vSavePoints = null;
    /** points for small image creating
     */
    private Vector<WeightPoint> m_vBrushPoint = null;
	/** to show back grid or not
	 */
	public boolean m_bShowGrid = true;
	/** read only or not
	 */
	private boolean m_bReadOnly = true;
	/** to show contour or not
	 */
	public boolean m_bShowStroke = true;
	/** to show middle line of stroke
	 */
	public boolean m_bShowSkeleton = true;
	/** to show start point of stroke
	 */
	private boolean m_bShowStartPoint = true;
	/** to enable call quadTo
	 */
	public boolean m_bEnableQuad = true;
	/** to enable creating score or not
	 */
	public boolean m_bEnableScore = true;
	/** to enable image cache or not
	 */
	public boolean m_bImageCache = true;
	/** 0: no grid; 1, TianGrid; 2. MiGrid; 3, NineGrid
	 */
	private int m_nBackGrid = 1;
	/** to show smooth contour or not
	 */
	private boolean mSmoothContour = false;
	/** to enable dash effect or not
	 */
	private boolean mEnableDash = true;

	/// for brush/pencil style
	public boolean m_bShowStyle = true;
	/// for auto-width scale adjust
	public boolean m_bAutoScale = true;
	/// maximum width of brush point
	public int m_nMaxPenWidth = 18;
	/// normal width of brush point
	public int m_nMidPenWidth = 9;
	/// minimum width of brush point
	public int m_nMinPenWidth = 4;
	/// step of change(no time case)
	public int m_iWidthStep = 6;
	/// to use time information or not
	public boolean m_bUsingTime = true;

	/// cached bitmap, for fast drawing
	private Bitmap mBitmap = null;
	/// canvas to hold cached bitmap
    private Canvas mCanvas = null;
    /// cached paint for drawing cached bitmap
    private Paint mBitmapPaint = null;
    /// cached paint for fast drawing line
    private Paint mLinePaint = null;
    private Paint mPaint = null;
    /// last pen width
    private int		mLastW = 0;
    /// last access point, x
    private float	mX = -1;
    /// last access point, y
    private float	mY = -1;
    /// rect to be updated
    private Rect mUpdateRect = null;
    private boolean	mUpdateStroke = true;
    private int mImageWidth = 0;
    private int mImageHeight = 0;

    public double m_fSlowSpeed = 0.15;//0.2;
    public double m_fNormalSpeed = 0.3;//0.50;
    public double m_fFastSpeed = 0.8;//5.0;
    public double m_fCurveSpeed = 0.5;
    public int mSmooth = 3;
    public int mCurveWidth = 6;
    private int mSlowCount = 0;
    /**
     * 0: no action; 1, brush; 2, quadTo; 3, fast timer; 4, lineTo
     */
    private int mStatus = 0;
    private double mLastSpeed = 0;
    private long mLastTick = 0;
    
    private String m_wordid = "";
    private Vector<StrokeRecord> mChnRecord = null;
    private FillThread mFillThread = null;
    
    public StrokeEventInterface mStrokeEvent = null;
    public QueryStroke mQueryCallback = null;
    
    private boolean    mZhuBiMode = false;
    
    /** to show oulines or not
     */
    public boolean m_bShowOutline = false;  //wang qian added on 201301023 for willwrite plugin
    
    public interface QueryStroke
    {
    	public Vector<StrokeRecord> getRecord(String strChn);
    	public ShortPoint[] getChnGuide(String sChn, int nStrokeIndex);
    };
    
	/** construction function( Used by dynamic create )
	 * initialize the class HandWritingController
	*/
	public StrokeView(Context context)
	{
		super(context);
		if (mPaint == null) {
			mPaint = new Paint();
			mPaint.setAntiAlias(true);
		}
	}

	/** construction function( Used by static create )
	 * initialize the class HandWritingController
	*/
	public StrokeView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		if (mPaint == null) {
			mPaint = new Paint();
			mPaint.setAntiAlias(true);
		}
	}
	
	/** construction function( Not Used )
	 * initialize the class HandWritingController
	*/
	public StrokeView(Context context, AttributeSet attrs, int defStyle)
	{
		super(context, attrs, defStyle);
		if (mPaint == null) {
			mPaint = new Paint();
			mPaint.setAntiAlias(true);
		}
	}
	
	@Override
	protected void onDetachedFromWindow() {
		super.onDetachedFromWindow();
		releaseAll();
	}
	
	public void close() {
		synchronized(this) {
			releaseAll();
		}
	}
	
	protected void releaseAll() {
		mQueryCallback = null;
		mStrokeEvent = null;
		CancelTimer();
		m_hHandler = null;
		m_sText = null;
		if (mBitmap != null) {
			mBitmap.recycle();
			mBitmap = null;
		}
		mCanvas = null;
		mBitmapPaint = null;
		mLinePaint = null;
		if (m_vSavePoints != null) {
			m_vSavePoints.clear();
			m_vSavePoints = null;
		}
		if (m_vBrushPoint != null) {
			m_vBrushPoint.clear();
			m_vBrushPoint = null;
		}
		if (mChnRecord != null) {
			int nSize = mChnRecord.size();
			for(int i=0; i<nSize; i++) {
				StrokeRecord sr = mChnRecord.get(i);
				if (sr != null) {
					sr.mContourLine = null;
					sr.mMiddleLine = null;
				}
			}
			mChnRecord.clear();
			mChnRecord = null;
		}
		if (mFillAgent != null) {
			mFillAgent.close();
			mFillAgent = null;
		}
		setBackgroundDrawable(null);
	}
	
	public boolean getReadOnly() {
		return m_bReadOnly;
	}
	
	/** set weather the StrokeView is readonly
	 * \param read boolean if readonly true, else false
	*/
	public void setReadOnly( boolean read ) {
		m_bReadOnly = read;
	}
	
	public boolean setGridType(int nGrid) {
		if (nGrid < 0 || nGrid > 3)
			return false;
		m_nBackGrid = nGrid;
		mUpdateStroke = true;
		invalidate();
		return true;
	}
	
	public int getGridType() {
		return m_nBackGrid;
	}
	
	public void enableDashEffect(boolean bEnable) {
		mEnableDash = bEnable;
		mUpdateStroke = true;
		invalidate();
	}
	
	public boolean isDashEffect() {
		return mEnableDash;
	}
	
	public boolean isShowStartPoint() {
		return m_bShowStartPoint;
	}
	
	public void showStartPoint(boolean bShow) {
		m_bShowStartPoint = bShow;
		mUpdateStroke = true;
		invalidate();
	}
	
	public void enableSmoothContour(boolean bEnable) {
		mSmoothContour = bEnable;
	}
	
	public boolean isSmoothContour() {
		return mSmoothContour;
	}
	
	public int getFillStyle() {
		return mFillStyle;
	}
	
	public boolean setFillStyle(int nFillStyle) {
		boolean bSucc = true;
		switch(nFillStyle) {
		case 0:
		case 1:
		case 2:
			stopStrokeAnimation();
			mFillStyle = nFillStyle;
			break;
		default:
			bSucc = false;
			break;
		}
		return bSucc;
	}
	
	public int getMaximumFillCount() {
		return mFillCount;
	}
	
	public boolean setMaximumFillCount(int nFillCount) {
		if (nFillCount <= 0)
			return false;
		mFillCount = Math.max(nFillCount / N, 1);
		return true;
	}
	
	public int getSpeedParam() {
		return N;
	}
	
	public boolean setSpeedParam(int n) {
		if (n<1 || n>5)
			return false;
		N = n;
		return true;
	}
	
	public int getFillBuShouColor() {
		return mFillBuShouColor;
	}
	
	public void setFillBuShouColor(int nFillColor) {
		mFillBuShouColor = nFillColor;
	}

	public int getFillBuYuColor() {
		return mFillBuYuColor;
	}
	
	public void setFillBuYuColor(int nFillColor) {
		mFillBuYuColor = nFillColor;
	}
	
	public int getFillWaitTime() {
		return mFillWaitTime;
	}
	
	public boolean setFillWaitTime(int nFillWaitTime) {
		if (nFillWaitTime <= 0)
			return false;
		mFillWaitTime = nFillWaitTime;
		return true;
	}
	
	/** clean all user writing on the StrokeView
	*/
	public void ReSet()
	{
		CancelTimer();
		clearMe();
	}
	
	/** get the text of StrokeView display now
	*/
	public String getText()
	{
		return m_sText;
	}
	
	public int getPaddingLeft() {
		return mPadding.left;
	}

	public int getPaddingTop() {
		return mPadding.top;
	}
	
	public int getPaddingRight() {
		return mPadding.right;
	}
	
	public int getPaddingBottom() {
		return mPadding.bottom;
	}

	/** set the distance from background to edge

	*/
	public void setPadding( int left, int top, int right, int bottom )
	{
		mPadding = new Rect(left, top, right, bottom);
	}
	
    /** set the text of StrokeView display now
     * @param wordid id of word
	 * @param text String the text that you want to display on the StrokeView
	*/
    public void SetText( String wordid, String text )
	{
		CancelTimer();
		clearMe();
		if( wordid==null || text==null ||
			wordid.length() <= 0 || text.length() != 1 )
			return;
		
		if( text.codePointAt(0) <= 128 )
			return;
		if (m_sText==null || m_wordid!=wordid || m_sText!=text)
		{
			mChnRecord = null;
			m_wordid = wordid;
			m_sText = text;
		}
		m_bReadOnly = false;
		mImageWidth = 0;
		mImageHeight = 0;
		mUpdateStroke = true;
		mLastW = 0;
		invalidate();
	}
    
    public int getStrokeColor() {
    	return m_iStrokeColor;
    }
    
    public void setStrokeColor(int nColor) {
    	m_iStrokeColor = nColor;
    	mUpdateStroke = true;
    }
    
    public int getBackColor() {
    	return m_iBackColor;
    }
    
    public void setBackColor(int nColor) {
    	m_iBackColor = nColor;
    }

    public int getLineColor() {
    	return m_iLineColor;
    }
    
	/** set the color of background line
	 * @param color int background line color
	*/
	public void SetLineColor( int color ) {
		m_iLineColor = color;
	}
	
	public int getWritingColor() {
		return m_iWriteColor;
	}
	/** set the color of user writing line
	 * @param color int user writing line color
	*/
	public void setWritingColor( int color )
	{
		m_iWriteColor = color;
		if (mLinePaint != null)
			mLinePaint.setColor(color);
		if (mBitmapPaint != null)
			mBitmapPaint.setColor(color);
	}
	
	public int getWritingWidth() {
		return (int)(m_iWriteWidth + 0.5f);
	}
	
	/** set the width of user writing line
	 * @param width int user writing line width
	*/
	public void setWritingWidth( int width )
	{
		m_iWriteWidth = width;
		if (mBitmapPaint != null)
			mBitmapPaint.setStrokeWidth(width);
		if (mLinePaint != null)
			mLinePaint.setStrokeWidth(width);
	}
	
	public boolean showStrokeAnimation() {
		if (TextUtils.isEmpty(m_sText))
			return false;
		if (getWidth()<=0 || getHeight()<=0 ||
			mChnRecord == null || mChnRecord.size() <= 0) {
			synchronized(this) {
				mNeedStartAnimation = true;
			}
		}
		else
			startAnimation();
		return true;
	}
	
	public boolean stopStrokeAnimation() {
		mStopFill = true;
		mFillStroke = -1;
		stopAnimationThread();
		return true;
	}

	private void stopAnimationThread() {
		if (mFillThread != null) {
			mFillThread.close();
			mFillThread = null;
		}
	}
	
	///////////////////////////////////////////////////////////////////////////
	
	class FillThread extends Thread {
	    private Boolean mPaused = false;
	    
	    public void pauseFillThread() {
	        synchronized (mPaused) {
	            mPaused = true;
	        }       
	    }
	    
	    public void continueFillThread() {
	        synchronized (mPaused) {
	            if (mPaused) {
	                mPaused.notify();
	                mPaused = false;
	            }
	        }       
	    }
		@Override
		public void run() {
			int nWait = Math.max(100 / N, 1);
			while(!mStopFill && (mFillStroke > 0)) {
				try{
				    Thread.sleep(nWait);
				    
					if (mFillStroke > 0) {
					    synchronized (mPaused) {
		                    if (mPaused) {
		                        mPaused.wait();
		                        mFillStroke = 1;
		                    } 
		                }
					    if (!mStopFill && (mFillStroke > 0)) {
					        onFillTimer();
					    }
					}	
				}catch(Exception e){
					Log.d(TAG, "3:" + e.getMessage());
				}
			}
			mStopFill = false;
		}
		
		public void close() {
			boolean bRetry = true;
			mStopFill = true;
			while(bRetry) {
	    		try{
	    			join();
	    			bRetry = false;
	    		}catch(Exception e){
	    			Log.d(TAG, "" + e.getMessage());
	    		}
			}
			mStopFill = false;
		}
	}
	
	private void startAnimation() {
		stopStrokeAnimation();
		CreateHandler();
		mFillStroke = N;
		if (mFillThread == null) {
			mStopFill = false;
			mFillThread = new FillThread();
			mFillThread.start();
		}
	}

	private void OnTimerID(int what) {
        switch (what) {
        case MSG_MOVE:
        	CancelTimer();
        	synchronized(this)
        	{
	        	DoCompleteLine();
				mStatus = 4;
        	}
        	break;
        }
	}
	
	private void onFillTimer() {
    	int nRes = 0;
		if (mFillStroke == 0)
			return;
		if (mFillStroke > 1) {
			mFillStroke--;
			return;
		}
		mAnimateRect = null;
		nRes = FillMorePoint(mFillCount);
		// wait 1.5 second then continue
		if (nRes > 0) {
			//Log.d(TAG, "fill="+nRes);
			if (mSmoothContour) {
				int nIdx = mFillAgent.getNextContourIndex() - 1;
				showSmoothContour(nIdx);
			}
			mFillStroke = mFillWaitTime * N;
			//mNotifyType = nRes;
		}
		if (mAnimateRect != null)
			postInvalidate(mAnimateRect.left+mPadding.left, mAnimateRect.top+mPadding.top, mAnimateRect.right+mPadding.left, mAnimateRect.bottom+mPadding.top);
		if (nRes > 0)
			notifyContourFilled(nRes);
	}
	
	private boolean showSmoothContour(int nIdx) {
		if (mFillAgent == null)
			return false;
		final ShortPoint[] spList = mFillAgent.getContour(nIdx);
		int nSize;
		if (spList != null && (nSize=spList.length) > 2) {
			// to create contour path
			Path path = new Path();
			path.moveTo(m_iMargin+spList[0].x, m_iMargin+spList[0].y);
			for(int i=1; i<nSize; i++)
				path.lineTo(m_iMargin+spList[i].x, m_iMargin+spList[i].y);
			path.close();
			if (mCanvas != null) {
				// to show contour
				Paint paint = new Paint();
				paint.setStyle(Style.FILL_AND_STROKE);
				paint.setStrokeWidth(2);
				paint.setAntiAlias(true);
				boolean bBuShou = mChnRecord.get(Math.max(nIdx, 0)).mBuShou;
				paint.setColor(bBuShou ? mFillBuShouColor : mFillBuYuColor);
				synchronized(this) {
					mCanvas.drawPath(path, paint);
					// to show start point every time, restore code here.
					//if (m_bShowStartPoint)
					//	showStartPoint(mCanvas, paint, nIdx);
					if (m_bShowStartPoint && (nIdx == mChnRecord.size() - 1)) {
						showStartPoint(mCanvas, paint, -1, m_bImageCache);
						mAnimateRect = null;
						postInvalidate();
						return true;
					}
				}
				// to create updating rectangle
				mAnimateRect = new Rect(m_iWidth, m_iHeight, 0, 0);
				for(int i=0; i<nSize; i++) {
					short x = spList[i].x;
					short y = spList[i].y;
					mAnimateRect.left = Math.min(mAnimateRect.left, x);
					mAnimateRect.right = Math.max(mAnimateRect.right, x + 1);
					mAnimateRect.top = Math.min(mAnimateRect.top, y);
					mAnimateRect.bottom = Math.max(mAnimateRect.bottom, y + 1);
				}
				// to enlarge updating rectangle
				mAnimateRect.left += m_iMargin-3;
				mAnimateRect.top += m_iMargin-3;
				mAnimateRect.right += m_iMargin+3;
				mAnimateRect.bottom += m_iMargin+3;
			}
		}
		if (mChnRecord.get(Math.max(nIdx, 0)).mStrokeEnd) {
		    if (mZhuBiMode) mFillThread.pauseFillThread();
		}
		return true;
	}
	
	private void notifyContourFilled(int nRes) {
		if (mStrokeEvent == null || mFillAgent == null) {
			if (nRes > 1) {
				stopStrokeAnimation();
				mFillAgent = null;
				mFillStroke = 0;
			}
			return;
		}
		int nContourIdx = mFillAgent.getNextContourIndex();
		StrokeRecord sr = mChnRecord.get(Math.max(nContourIdx - 1, 0));
		if (!sr.mStrokeEnd || mStopFill)
			return;
		int nWait = mStrokeEvent.OnFinishedListerner(this, sr.mStrokeName);
		if (nWait <= 0)
		{
			// if not the last contour, do nothing; else, to send end notify
			if (nContourIdx < mChnRecord.size() - 1)
				return;
		}
		if (nRes == 1) {
			mFillStroke += nWait * N;
			return;
		}
		// wait a moment, then re-send notify
		try{
			Thread.sleep(nWait * 100);
		}catch(Exception e){
			Log.d(TAG, "sleep:" + e.getMessage());
		}
		mStrokeEvent.OnFinishedListerner(this, null);
		mFillStroke = 0;
		mStopFill = true;
		mFillAgent = null;
	}
	
	/** to fill more point for stroke-filling
	 * @param nPoints at most filling-count
	 * @return -1: argument error; 0: normal end; 1, stroke end; 2, ChnWord end
	 */
	private int FillMorePoint(int nPoints) {
		if (nPoints <= 0 || mChnRecord == null)
			return -1;
		int nSize = mChnRecord.size();
		if (nSize <= 0)
			return -1;
		SafeEnableBitmapCache();
		if (mFillAgent == null) {
			mFillAgent = new FillStroke(mChnRecord, getWidth()-mPadding.left-mPadding.right-m_iMargin*2, getHeight()-mPadding.top-mPadding.bottom-m_iMargin*2, mFillStyle);
			mFillAgent.enableQueryGuide(new FillStroke.IQueryStrokeGuide() {
				@Override
				public ShortPoint[] getStrokeGuide(String sChn, int nStrokeIndex) {
					// TODO Auto-generated method stub
					if (mQueryCallback == null)
						return null;
					return mQueryCallback.getChnGuide(sChn, nStrokeIndex);
				}
			});
		}
		Vector<ShortPoint> vsp = new Vector<ShortPoint>();
		int nCount;
		int nRes = mFillAgent.getNextFillGroup(vsp, nPoints);
		if (nRes >= 0 && (nCount=vsp.size()) > 0) {
			float[] f = new float[nCount * 2];
			mAnimateRect = new Rect(getWidth(), getHeight(), 0, 0);
			for(int i=0; i<nCount; i++) {
				ShortPoint sp = vsp.get(i);
				int x = m_iMargin + sp.x;
				int y = m_iMargin + sp.y;
				f[2 * i + 0] = x;
				f[2 * i + 1] = y;
				// to update rectangle range
				mAnimateRect.left = Math.min(x, mAnimateRect.left);
				mAnimateRect.top = Math.min(y, mAnimateRect.top);
				mAnimateRect.right = Math.max(x + 1, mAnimateRect.right);
				mAnimateRect.bottom = Math.max(y + 1, mAnimateRect.bottom);
			}
			Paint paint = new Paint();
			int nIdx = mFillAgent.getNextContourIndex();
			if (nRes > 0)
				nIdx--;
			boolean bBuShou = mChnRecord.get(Math.max(nIdx, 0)).mBuShou;
			paint.setColor(bBuShou ? mFillBuShouColor : mFillBuYuColor);
			synchronized(this) {
				mCanvas.drawPoints(f, paint);
			}
		}
		return nRes;
	}
	
	private void DoCompleteLine() {
    	if ((mStatus==3 || mStatus==2) && m_vSavePoints!=null && m_vSavePoints.size()>0) {
    		ShortPoint wpEnd = m_vSavePoints.elementAt(m_vSavePoints.size() - 1);
    		if (mCanvas!=null && mLinePaint!=null && wpEnd.x>=0 && wpEnd.y>=0) {
    			WeightPoint wp = new WeightPoint(wpEnd.x, wpEnd.y, mLastW);
    			Brush.lineTo(mCanvas, mBitmapPaint, wp);
    			SaveBrushPoint(wp.mX, wp.mY, wp.mW);
    		}
			ShortPoint wp_0 = new ShortPoint();
			wp_0.x = (short)mX;
			wp_0.y = (short)mY;
   			InvalidatePoint(wp_0, wpEnd);
    	}
	}
	
	private boolean CreateHandler()
	{
		if (m_hHandler != null)
			return true;
		m_hHandler = new Handler(){
            public void handleMessage(Message msg) {
            	super.handleMessage(msg);
            	OnTimerID(msg.what);
            }
       };
       return (m_hHandler != null);
	}

    private void clearBrushPoint() {
    	if (m_vBrushPoint != null)
    		m_vBrushPoint.clear();
    }
    
    private void SaveBrushPoint(float x, float y, float w) {
    	WeightPoint wp = new WeightPoint(x, y, w);
    	if (m_vBrushPoint == null)
    		m_vBrushPoint = new Vector<WeightPoint>();
    	m_vBrushPoint.add(wp);
    }

	private void CancelTimer() {
		if (m_mTimer != null) {
			m_mTimer.cancel();
			m_mTimer = null;
		}
		if (m_mTimerTask != null) {
			m_mTimerTask.cancel();
			m_mTimerTask = null;
		}
		stopStrokeAnimation();
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if ( m_bReadOnly) {
			if (mStrokeEvent != null) {
			    if (event.getAction() == MotionEvent.ACTION_UP)
			        mStrokeEvent.OnClickView(this);
			}	
			return true;//super.onTouchEvent(event);
		}
		CancelTimer();
		mStatus = 0;
    	float x = event.getX();
    	float y = event.getY();
		long nTime = event.getEventTime();
		switch (event.getAction())
		{
		case MotionEvent.ACTION_DOWN:
			setNewPoint( (int)(x+0.5f), (int)(y+0.5f) );
			mLastTick = nTime;
			mX = x;
			mY = y;
			mSlowCount = 0;
			mLastW = m_nMaxPenWidth;
			Brush.moveTo(new WeightPoint(mX, mY, mLastW));
			SaveBrushPoint(mX, mY, mLastW);
			break;
		case MotionEvent.ACTION_MOVE:
			synchronized(this)
			{
				if (setNewPoint( (int)(x+0.5f), (int)(y+0.5f) ))
					AfterMove(nTime);
			}
			mLastTick = nTime;
			break;
        case MotionEvent.ACTION_UP:
//        	Log.d(TAG, "end ------ stroke");
        	setNewPoint( -1, -1 );
        	SaveBrushPoint(-1, -1, 0);
        	DoMoveUp(x, y);
        	break;
        }

		return true;
	}

	private void DoMoveUp(float x, float y) {
		WeightPoint wpEnd = new WeightPoint(x, y, mLastW);
		Brush.lineTo(mCanvas, mBitmapPaint, wpEnd);
		ShortPoint p0 = new ShortPoint();
		p0.x = (short)mX;
		p0.y = (short)mY;
		ShortPoint p1 = new ShortPoint();
		p1.x = (short)x;
		p1.y = (short)y;
		InvalidatePoint(p0, p1);

		mX = 0;
		mY = 0;
		mLastW = 0;
	}

	private static float GetDist2(ShortPoint pt1, ShortPoint pt2) {
		float x = pt1.x - pt2.x;
		float y = pt1.y - pt2.y;
		return (float) Math.sqrt(x * x + y * y);
	}
	
	private boolean AfterMove(long nTick) {
		int nSize = m_vSavePoints.size();
		if (nSize < 2)
			return false;
		ShortPoint wp_1 = m_vSavePoints.get(nSize-2);
		ShortPoint wp_2 = m_vSavePoints.get(nSize-1);
		if (wp_1.x<0 || wp_1.y<0 || wp_2.x<0 || wp_2.y<0)
			return false;
		if (mLastW == 0)
			mLastW = m_nMaxPenWidth;
		int w = mLastW;
		if (m_bAutoScale)
		{
			int nMaxDist = (int)GetDist2(wp_1, wp_2);
			if (m_bUsingTime)
			{
				double speed = CalculateSpeed( nSize-1, nTick );
				if( speed > 0 )
					w = (int)(CalculateWidthBySpeed( speed ) + 0.5f);
				mLastSpeed = speed;
			}
			else
			{
				int nDist = Math.max(nMaxDist, 1);
				int nScale = nDist / m_iWidthStep;
				if (nScale <= 1){	// slow
					w = mLastW + 1;
				}else if(nScale <= 3){	// mid
					if (mLastW > m_nMidPenWidth)
						w = mLastW - nScale;
					else if(mLastW < m_nMidPenWidth)
						w = mLastW + nScale;
				}else{	// fast
					if (mLastW >= m_nMidPenWidth)
						w = mLastW - nScale;
				}
			}
			// to prevent from too hard change
			int yMin, yMax;
			int nStep = nMaxDist;
			if (mSmooth > 1)
				nStep = (nMaxDist + mSmooth - 1) / mSmooth;
			// to set user's limitation
			if (w > (yMax= Math.min(mLastW + nStep, m_nMaxPenWidth)))
				w = yMax;
			else if(w < (yMin= Math.max(mLastW - nStep, m_nMinPenWidth)))
				w = yMin;
			if (w > mLastW) {
				if (nStep > 1 || mSlowCount>=3) {
					w = mLastW + 1;
					mSlowCount = 0;
				}else{
					w = mLastW;
					mSlowCount++;
				}
			} else {
				mSlowCount = 0;
				if (w <= mCurveWidth && mLastW > mCurveWidth) {
					w = mCurveWidth;
					// bQuadTo will be false, should not be too abrupt, minimum to mCurveWidth
					int n = Math.max(mSmooth, 3);
					int nMaxRange = (mLastW - mCurveWidth) * n;
					if (nMaxRange < nMaxDist) {
						float fR = nMaxRange / (float)nMaxDist;
						float mMoveX = wp_1.x + fR * (wp_2.x - wp_1.x);
						float mMoveY = wp_1.y + fR * (wp_2.y - wp_1.y);
						WeightPoint wp1 = new WeightPoint(wp_1.x, wp_1.y, mLastW);
						WeightPoint wp2 = new WeightPoint(mMoveX, mMoveY, w);
						if (mStatus == 3){
							WeightPoint wp0 = new WeightPoint(mX, mY, mLastW);
							Brush.quadTo(mCanvas, mBitmapPaint, wp0, wp1);
							SaveBrushPoint(wp1.mX, wp1.mY, wp1.mW);
							mStatus = 0;
						}
						Brush.quadTo(mCanvas, mBitmapPaint, wp1, wp2);
						SaveBrushPoint(wp2.mX, wp2.mY, wp2.mW);
						mX = mMoveX;
						mY = mMoveY;
						mLastW = w;
					}
				}
			}
//			Log.d(TAG, "w="+w+": x1="+wp_1.x+",y1="+wp_1.y+"->x2="+wp_2.x+",y2="+wp_2.y);
			if (w<mLastW && mLastW<=mCurveWidth) {
				w = Math.max(mLastW - 1, m_nMinPenWidth);
			}
			mLastW = w;
		}
		else
		{
			w = m_nMaxPenWidth;
		}

//		Log.d(TAG, "mX="+mX+",mY="+mY+";endX="+wp_2.x+",endY="+wp_2.y);
		if (Math.abs(mX - wp_2.x)>=2 || Math.abs(mY - wp_2.y)>=2)
		{
			float fX = (mX + wp_2.x) / 2;
			float fY = (mY + wp_2.y) / 2;
			float fW = (mLastW + w) / 2.0f;
			WeightPoint wp2 = new WeightPoint(fX, fY, fW);
			WeightPoint wp1 = new WeightPoint(mX, mY, mLastW);
			Brush.quadTo(mCanvas, mBitmapPaint, wp1, wp2);
			SaveBrushPoint(wp2.mX, wp2.mY, wp2.mW);
			mX = wp_2.x;
			mY = wp_2.y;
			mLastW = w;
			mStatus = 2;
			StartFastTimer();
		}
		else
		{
			// can not update, for status 2->3
			mStatus = 3;
			mLastW = w;
			StartFastTimer();
		}
		InvalidatePoint(wp_1, wp_2);
		return true;
	}
	
	private void StartFastTimer() {
		CreateHandler();
		m_mTimer = new Timer();
        m_mTimerTask = new TimerTask()
        {
             @Override
             public void run() {
                 m_hHandler.sendEmptyMessage(MSG_MOVE);
                 }
        };
		m_mTimer.schedule(m_mTimerTask, 100);
	}

	private void InvalidatePoint(ShortPoint wp_1, ShortPoint wp_2) {
		Rect rect = new Rect();
		rect.left = Math.min(wp_1.x, wp_2.x) - m_nMaxPenWidth;
		rect.right = Math.max(wp_1.x, wp_2.x) + m_nMaxPenWidth + 1;
		rect.top = Math.min(wp_1.y, wp_2.y) - m_nMaxPenWidth;
		rect.bottom = Math.max(wp_1.y, wp_2.y) + m_nMaxPenWidth + 1;
		rect.left = Math.max(rect.left, 0);
		rect.right = Math.min(rect.right, m_iWidth);
		rect.top = Math.max(rect.top, 0);
		rect.bottom = Math.min(rect.bottom, m_iHeight);
		if (mUpdateRect == null)
			mUpdateRect = new Rect();
		if (!rect.isEmpty())
			mUpdateRect.union(rect);
		postInvalidate(mUpdateRect.left-50, mUpdateRect.top-50, mUpdateRect.right+50, mUpdateRect.bottom+50);
	}

	public BrushMetric createBinary(boolean bPunct, boolean bClone) {
		return createMetrics(bPunct, bClone);
	}
	
	public boolean loadBinary(BrushMetric bm) {
		// to create image
		int nW = getHeight();
		float fScale = nW / (float)bm.mMinSize;
		mBitmap = createScaleImage(nW, nW, fScale, bm, Color.WHITE, Color.BLUE);
		if (mBitmap == null)
			return false;
		invalidate();
		return true;
	}
	
    static private Bitmap createScaleImage(int nW, int nH, float fScale, BrushMetric bm, int clrBack, int clrStroke) {
    	Bitmap bmpSave = null;
		if (bm.mPunct) {
    		float fMinSize = Math.min(bm.mXScale, bm.mYScale) - 20.0f;
    		float fXScale = fMinSize / bm.mXScale;
    		float fYScale = fMinSize / bm.mYScale;
    		int nWMax = (int)(nW / fXScale + 0.5f);
    		int nHMax = (int)(nH / fYScale + 0.5f);
    		// to create image from wpList
       		Bitmap bmp = createScaleImage(nWMax, nHMax, bm.mXScale, bm.mYScale, bm.mMaxPenWidth, fScale, bm.mList, clrBack, clrStroke, 0, 0);
       		if (bmp != null) {
       			bmpSave = Bitmap.createBitmap(bmp, (nWMax - nW) / 2, (nHMax - nH) / 2, nW, nH);
       			bmp = null;
       		}
		}else{
			Rect rect = getPointOutRect(bm.mList, bm.mXScale, bm.mYScale);
			float fMinSize = Math.max(rect.right - rect.left, rect.bottom - rect.top);
			float fLeft = (rect.left + rect.right) / 2.0f - fMinSize / 2.0f;
			float fTop = (rect.top + rect.bottom) / 2.0f - fMinSize / 2.0f;
			float fXScale = fMinSize / bm.mXScale;
			float fYScale = fMinSize / bm.mYScale;
    		int nWMax = (int)(nW / fXScale + 0.5f);
    		int nHMax = (int)(nH / fYScale + 0.5f);
    		int nWMax1 = Math.max(nWMax, nW);
    		int nHMax1 = Math.max(nHMax, nH);
    		// to create image from wpList
    		int xOffset = (nWMax1 - nWMax) / 2;
    		int yOffset = (nHMax1 - nHMax) / 2;
       		Bitmap bmp = createScaleImage(nWMax1, nHMax1, bm.mXScale, bm.mYScale, bm.mMaxPenWidth, fScale, bm.mList, clrBack, clrStroke, xOffset, yOffset);
       		if (bmp != null) {
   				xOffset += nWMax1 * (int) Math.max(fLeft + EPSILON, 0) / bm.mXScale;
       			yOffset += nHMax1 * (int) Math.max(fTop + EPSILON, 0) / bm.mYScale;
       			if (xOffset+nW > nWMax1)
       				xOffset = nWMax1 - nW;
       			if (yOffset+nH > nHMax1)
       				yOffset = nHMax1 - nH;
       			bmpSave = Bitmap.createBitmap(bmp, xOffset, yOffset, nW, nH);
       			bmp = null;
       		}
		}
		return bmpSave;
    }

    static private Bitmap createScaleImage(int nW, int nH, int nMaxW, int nMaxH, int nMaxWeight, float fScale, Vector<WeightPoint> wpList, int clrBack, int clrStroke, int xOffset, int yOffset) {
    	Bitmap bmp = null;
    	try{
    		if (nW<=1 || nH<=1 || nMaxW<=1 || nMaxH<=1)
    			return null;
    		// to create image from m_vBrushPoint
       		bmp = Bitmap.createBitmap(nW, nH, Config.ARGB_8888);
       		if (bmp == null)
       			return null;
       		Canvas canvas = new Canvas();
       		canvas.setBitmap(bmp);
       		boolean bMove = true;
       		int nSize = wpList.size();
       		Paint paint = new Paint(Paint.DITHER_FLAG);
       		paint.setStyle(Paint.Style.FILL);
       		paint.setColor( clrStroke );
       		paint.setStrokeWidth(Math.max(nMaxWeight * fScale, 1.0f));
       		paint.setPathEffect(null);
       		paint.setAntiAlias(true);
       		canvas.drawColor(clrBack);
       		float xScale = nW / (float)nMaxW;
       		float yScale = nH / (float)nMaxH;
       		boolean bQuadTo = false;
       		WeightPoint wp0 = null;
       		for(int i=0; i<nSize; i++) {
       			WeightPoint wp = wpList.get(i);
       			if (bMove) {
           			wp0 = new WeightPoint(xOffset+wp.mX*xScale, yOffset+wp.mY*yScale, Math.max(wp.mW * fScale, 1));
					Brush.moveTo(wp0);
           			bMove = false;
       			}
       			else if(i > 0 && wp.mX>-EPSILON && wp.mY>-EPSILON) {
           			WeightPoint wpLast = wpList.get(i);
           			WeightPoint wp_1 = new WeightPoint(xOffset+wpLast.mX*xScale, yOffset+wpLast.mY*yScale, Math.max(wpLast.mW * fScale, 1));
           			WeightPoint wp_2 = new WeightPoint(xOffset+wp.mX*xScale, yOffset+wp.mY*yScale, Math.max(wp.mW * fScale, 1));
					Brush.quadTo(canvas, paint, wp_1, wp_2);
       				bQuadTo = true;
       				wp0 = null;
       			}else if(wp.mX < 0 && wp.mY < 0) {
       				if (!bQuadTo)
						Brush.lineTo(canvas, paint, wp0);
       				bMove = true;
       				bQuadTo = false;
       			}
       		}
       		canvas = null;
    	}catch(Exception e){
    		bmp = null;
    		Log.d(TAG, "1:" + e.getMessage());
    	}
    	return bmp;
    }

    static private Rect getPointOutRect(Vector<WeightPoint> wpList, int nMaxW, int nMaxH) {
		float xMin = nMaxW;
		float yMin = nMaxH;
		float xMax = 0;
		float yMax = 0;
		int nSize = wpList.size();
		for(int i=0; i<nSize; i++) {
			WeightPoint wp = wpList.get(i);
			if (wp.mX<-EPSILON && wp.mY<-EPSILON)
				continue;
			xMin = Math.min(xMin, wp.mX);
			xMax = Math.max(xMax, wp.mX);
			yMin = Math.min(yMin, wp.mY);
			yMax = Math.max(yMax, wp.mY);
		}
		xMin = Math.max(xMin - 20, 0);
		xMax = Math.min(xMax + 20, nMaxW);
		yMin = Math.max(yMin - 20, 0);
		yMax = Math.min(yMax + 20, nMaxH);
		Rect rect = new Rect((int)xMin, (int)yMin, (int)xMax, (int)yMax);
		return rect;
    }

    private BrushMetric createMetrics(boolean bPunct, boolean bClone) {
    	Vector<WeightPoint> wpList = m_vBrushPoint;
    	if (wpList == null || wpList.size() <= 0)
    		return null;

    	int nWidth = getWidth();
    	int nHeight = getHeight();
		int nMinSize = Math.min(nWidth - mPadding.left - mPadding.right, nHeight - mPadding.top - mPadding.bottom);
		int nXScale = nWidth;
		int nYScale = nHeight;
		int nXOffset = (nXScale - nMinSize) / 2;
		int nYOffset = (nYScale - nMinSize) / 2;

    	BrushMetric bm = new BrushMetric();
    	if (bClone) {
    		bm.mList = new Vector<WeightPoint>();
    		int nSize = wpList.size();
    		bm.mList.ensureCapacity(nSize);
    		for(int i=0; i<nSize; i++) {
    			WeightPoint wp0 = wpList.get(i);
    			WeightPoint wp1 = new WeightPoint();
    			wp1.mX = wp0.mX;
    			wp1.mY = wp0.mY;
    			wp1.mW = wp0.mW;
    			bm.mList.add(i, wp1);
    		}
    	}else{
    		bm.mList = wpList;
    	}
    	bm.mMaxPenWidth = m_nMaxPenWidth;
    	bm.mMinSize = nMinSize;
    	bm.mPunct = bPunct;
    	bm.mXOffset = nXOffset;
    	bm.mYOffset = nYOffset;
    	bm.mXScale = nXScale;
    	bm.mYScale = nYScale;

    	return bm;
    }

	public short[] GetRecognitionPoints() {
		if (m_vSavePoints == null)
			return null;
		int count = m_vSavePoints.size();
		if( count == 0 )
			return null;
		short[] ptList = new short[count * 2 + 2];
		for(int i=0; i<count; i++)
		{
			ShortPoint wp = m_vSavePoints.elementAt(i);
			if (wp.x != -1 && wp.y != -1)
			{
				ptList[2*i+0] = wp.x;
				ptList[2*i+1] = wp.y;
			}
			else
			{
				ptList[2*i+0] = (short)-1;
				ptList[2*i+1] = (short)0;
			}
		}
		ptList[2*count+0] = (short)-1;
		ptList[2*count+1] = (short)-1;
		return ptList;
	}

	@Override
	public void onWindowFocusChanged(boolean hasWindowFocus) {
		super.onWindowFocusChanged(hasWindowFocus);
		if (!hasWindowFocus)
			return;
		m_iWidth = getWidth();
		m_iHeight = getHeight();
		if (m_bEnableScore)
			SafeEnableBitmapCache();
	}

	private void clearMe() {
		if ( m_vSavePoints != null ) {
			m_vSavePoints.clear();
			m_vSavePoints = null;
		}
		mLastW = 0;
		clearBrushPoint();
		synchronized(this)
		{
			mUpdateStroke = true;
		}
		m_sText = "";
		if (mFillAgent != null)
			mFillAgent.enableQueryGuide(null);
		mFillAgent = null;
		invalidate();
	}

	private boolean setNewPoint( int x, int y ) {
		if (m_bReadOnly)
			return false;
		if (m_vSavePoints==null && (m_vSavePoints=new Vector<ShortPoint>())==null)
			return false;
		int count = m_vSavePoints.size();
		if ( x != -1 && y != -1 && count > 0) {
			ShortPoint m_1 = m_vSavePoints.elementAt(count-1);
			if ( m_1.x==x && m_1.y==y)
				return false;
			// to remove some point on one line
			if ( count > 1 && !m_bUsingTime) {
				ShortPoint m_2 = m_vSavePoints.elementAt(count-2);
				if (m_1.x >= 0 && m_2.x >= 0 )
				{
					int a = (m_1.y - m_2.y) * (x - m_1.x);
					int b = (y - m_1.y) * (m_1.x - m_2.x);
					if ( a == b )
						m_vSavePoints.remove(count-1);
				}
			}
		}
		// to add new point
		ShortPoint wp = new ShortPoint();
		wp.x = (short)x;
		wp.y = (short)y;
		m_vSavePoints.add(wp);
		return true;
	}

	void enablePaintDashEffect(Paint paint, int nMax) {
		float dashwidth = nMax / 50.0f;
		dashwidth = Math.max(dashwidth, 1);
		float[] fPath = new float[]{dashwidth, dashwidth, dashwidth, dashwidth};
		if (fPath != null) {
			PathEffect PEffect = new DashPathEffect( fPath, 1 );
			if (PEffect != null)
				paint.setPathEffect( PEffect );
		}
	}

    void drawBackTian(Canvas canvas, int nMaxW, int nMaxH, int nColor) {
		int nMax = Math.min(nMaxW, nMaxH) - m_iMargin*2;
		int xOffset = (nMaxW - nMax) / 2;
		int yOffset = (nMaxH - nMax) / 2;

		Paint paint = new Paint();
		paint.setStyle(Paint.Style.STROKE);
		paint.setColor(nColor);
		paint.setStrokeWidth(1);
		int xMax = xOffset + nMax;
		int yMax = yOffset + nMax;

		if (mEnableDash)
			enablePaintDashEffect(paint, nMax);

		canvas.drawLine(xOffset, yOffset, xMax, yOffset, paint);
		canvas.drawLine(xOffset, yMax, xMax, yMax, paint);
		canvas.drawLine(xOffset, yOffset, xOffset, yMax, paint);
		canvas.drawLine(xMax, yOffset, xMax, yMax, paint);
		canvas.drawLine(xOffset, nMaxH/2, xOffset+nMax, nMaxH/2, paint);
		canvas.drawLine(nMaxW/2, yOffset, nMaxW/2, yOffset+nMax, paint);
    }

	void drawBackMiZi(Canvas canvas, int nMaxW, int nMaxH, int clrLine) {
		int nMax = Math.min(nMaxW, nMaxH) - m_iMargin*2;
		int xOffset = (nMaxW - nMax) / 2;
		int yOffset = (nMaxH - nMax) / 2;

		Paint paint = new Paint();
		paint.setStyle(Paint.Style.STROKE);
		paint.setColor(clrLine);
		paint.setStrokeWidth(1);
		int xMax = xOffset + nMax;
		int yMax = yOffset + nMax;
		canvas.drawLine(xOffset, yOffset, xMax, yOffset, paint);
		canvas.drawLine(xOffset, yMax, xMax, yMax, paint);
		canvas.drawLine(xOffset, yOffset, xOffset, yMax, paint);
		canvas.drawLine(xMax, yOffset, xMax, yMax, paint);


		if (mEnableDash)
			enablePaintDashEffect(paint, nMax);

        canvas.drawLine(xOffset, yOffset, xMax, yMax, paint);
        canvas.drawLine(xMax, yOffset, xOffset, yMax, paint);
		canvas.drawLine(xOffset, nMaxH/2, xOffset+nMax, nMaxH/2, paint);
		canvas.drawLine(nMaxW/2, yOffset, nMaxW/2, yOffset+nMax, paint);
    }

    void drawBackNine(Canvas canvas, int nMaxW, int nMaxH, int nColor) {
		int nMax = Math.min(nMaxW, nMaxH) - m_iMargin*2;
		int xOffset = (nMaxW - nMax) / 2;
		int yOffset = (nMaxH - nMax) / 2;

		Paint paint = new Paint();
		paint.setStyle(Paint.Style.STROKE);
		paint.setColor(nColor);
		paint.setStrokeWidth(1);
		int xMax = xOffset + nMax;
		int yMax = yOffset + nMax;

		if (mEnableDash)
			enablePaintDashEffect(paint, nMax);

		canvas.drawLine(xOffset, yOffset, xMax, yOffset, paint);
		canvas.drawLine(xOffset, yMax, xMax, yMax, paint);
		canvas.drawLine(xOffset, yOffset, xOffset, yMax, paint);
		canvas.drawLine(xMax, yOffset, xMax, yMax, paint);
		canvas.drawLine(xOffset, yOffset+nMax/3, xOffset+nMax, yOffset+nMax/3, paint);
		canvas.drawLine(xOffset, yOffset+nMax*2/3, xOffset+nMax, yOffset+nMax*2/3, paint);
		canvas.drawLine(xOffset+nMax/3, yOffset, xOffset+nMax/3, yOffset+nMax, paint);
		canvas.drawLine(xOffset+nMax*2/3, yOffset, xOffset+nMax*2/3, yOffset+nMax, paint);
    }
    /** to draw background
     * @param paint
     * @param canvas
     * @param iWidth width, may not including padding
     * @param iHeight height, may not including padding
     * @param bCache true, for image cache; false, for direct drawing
     */
	private void drawBackGround( Paint paint, Canvas canvas, int iWidth, int iHeight, boolean bCache ) {
		if (paint == null || canvas == null || iWidth <= 0 || iHeight <= 0 || !m_bShowGrid)
			return;
		canvas.drawColor(m_iBackColor);
		switch(m_nBackGrid)
		{
		case 1:
			drawBackTian(canvas, iWidth, iHeight, m_iLineColor);
			break;
		case 2:
			drawBackMiZi(canvas, iWidth, iHeight, m_iLineColor);
			break;
		case 3:
			drawBackNine(canvas, iWidth, iHeight, m_iLineColor);
			break;
		default:
			break;
		}
		if (TextUtils.isEmpty(m_sText) || mQueryCallback==null)
			return;
		if (mChnRecord == null || mChnRecord.size() <= 0) {
			mChnRecord = mQueryCallback.getRecord(m_sText);
		}
		if (m_bShowStroke || m_bShowOutline)
			ShowAllStrokes(canvas, paint, bCache);
		if (m_bShowSkeleton)
			ShowMidLines(canvas, paint, true, bCache);
		if (m_bShowStroke && m_bShowStartPoint)
			showStartPoint(canvas, paint, -1, bCache);
		paint.setColor( m_iWriteColor );
		paint.setStyle(Style.FILL);
	}

	private boolean ShowAllStrokes(Canvas canvas, Paint paint, boolean bCache) {
		if (mChnRecord == null || mChnRecord.size() <= 0)
			return false;

		if (m_iStrokeColor != 0)
			paint.setColor( m_iStrokeColor );
		if (m_bShowOutline) {
            paint.setStyle(Paint.Style.STROKE);
            paint.setPathEffect(new DashPathEffect(new float[]{5,5,5,5}, 1));
            paint.setStrokeWidth(2);
        } else {
            paint.setStyle(Paint.Style.FILL);
            paint.setStrokeWidth(3);
            paint.setPathEffect(null);
        }


		float fW = (getWidth() - mPadding.left - mPadding.right - m_iMargin * 2) / 267.0f;
		float fH = (getHeight() - mPadding.top - mPadding.bottom - m_iMargin * 2) / 267.0f;
		int xOffset = m_iMargin;
		int yOffset = m_iMargin;
		if (!bCache) {
			xOffset += mPadding.left;
			yOffset += mPadding.top;
		}
		int nSize = mChnRecord.size();
		for(int i=0; i<nSize; i++) {
			StrokeRecord sr = mChnRecord.get(i);
			if (sr == null)
				continue;
			ShortPoint[] wp = sr.mContourLine;
			if (wp == null || wp.length < 2)
				continue;
			Path path = new Path();
			path.moveTo(wp[0].x * fW + xOffset, wp[0].y * fH + yOffset);
			for(int j=1; j<wp.length; j++)
				path.lineTo(wp[j].x * fW + xOffset, wp[j].y * fH + yOffset);
			path.close();
			if (m_iStrokeColor == 0) {
				if (sr.mBuShou)
					paint.setColor(mFillBuShouColor);
				else
					paint.setColor(mFillBuYuColor);
			}
			canvas.drawPath(path, paint);
		}
		return true;
	}

	private boolean ShowMidLines(Canvas canvas, Paint paint, boolean bShowEndAngle, boolean bCache) {
		if (mChnRecord == null || mChnRecord.size() <= 0)
			return false;
		paint.setColor( Color.RED );
		paint.setStyle(Paint.Style.STROKE);
		paint.setStrokeWidth(2);

		float fW = (getWidth() - mPadding.left - mPadding.right - m_iMargin * 2) / 267.0f;
		float fH = (getHeight() - mPadding.top - mPadding.bottom - m_iMargin * 2) / 267.0f;
		int nSize = mChnRecord.size();
		int xOffset = m_iMargin;
		int yOffset = m_iMargin;
		if (!bCache) {
			xOffset += mPadding.left;
			yOffset += mPadding.top;
		}
		for(int i=0; i<nSize; i++) {
			StrokeRecord sr = mChnRecord.get(i);
			if (sr == null)
				return false;
			ShortPoint[] wp = sr.mMiddleLine;
			if (wp == null || wp.length < 2)
				continue;
			Path path = new Path();
			path.moveTo(wp[0].x * fW + xOffset, wp[0].y * fH + yOffset);
			for(int j=1; j<wp.length; j++)
				path.lineTo(wp[j].x * fW + xOffset, wp[j].y * fH + yOffset);
			canvas.drawPath(path, paint);
			if (bShowEndAngle && sr.mStrokeEnd) {
				float fAngle = sr.mEndAngle;
				float f1 = (fAngle - 30) * PI / 180;
				float f2 = (fAngle + 30) * PI / 180;

				int x = (int)(wp[wp.length-1].x * fW + 0.5f) + xOffset;
				int y = (int)(wp[wp.length-1].y * fH + 0.5f) + yOffset;

				int x0 = (int)(x + 10 * Math.cos(f1) + 0.5f);
				int y0 = (int)(y - 10 * Math.sin(f1) + 0.5f);
				int x1 = (int)(x + 10 * Math.cos(f2) + 0.5f);
				int y1 = (int)(y - 10 * Math.sin(f2) + 0.5f);

				canvas.drawLine( x, y, x0, y0, paint);
				canvas.drawLine( x, y, x1, y1, paint);
			}
		}
		return true;
	}

	protected boolean showStartPoint(Canvas canvas, Paint paint, int nContourIndex, boolean bCache) {
		paint.setStyle(Paint.Style.FILL);
		paint.setTextSize(m_idefault8);
		int j = 1;
		boolean bSkip = false;
		float fW = (m_iWidth - m_iMargin * 2) / 267.0f;
		float fH = (m_iHeight - m_iMargin * 2) / 267.0f;
		int nSize = mChnRecord.size();
		int xOffset = m_iMargin;
		int yOffset = m_iMargin;
		if (!bCache) {
			xOffset += mPadding.left;
			yOffset += mPadding.top;
		}


		for( int i = 0; i < nSize; i++)
		{
			StrokeRecord sr = mChnRecord.get(i);

			if (i > 0 && bSkip) {
				bSkip = !sr.mStrokeEnd;
				continue;
			}
			bSkip = !sr.mStrokeEnd;


			if ((nContourIndex < 0) || (nContourIndex >= 0) && (i == nContourIndex)) {
				float x = sr.mStartX * fW + xOffset;
				float y = sr.mStartY * fH + yOffset;
				paint.setColor( 0xffffffff );
				canvas.drawCircle( x, y, m_idefault8, paint);
				paint.setColor( 0xff0000ff );
				canvas.drawText( j+"", x-m_idefault4, y+m_idefault4, paint);
			}
			j++;
		}
		return true;
	}
	/** to create stroke image cache.
	 * @param iWidth width, not including padding
	 * @param iHeight height, not including padding
	 * @return
	 */
	protected boolean EnableImageCache(int iWidth, int iHeight) {
		if (iWidth <= 0 || iHeight <= 0)
			return false;
		if (mBitmap == null) {
			// bypass 2012-12-14 begin:
			// ���7�磬android4.1,Forclass�����̲ģ��������Ч��Ԥ���ı���ɫ������Ч��ͼ����
			// ���7�磬android4.1,Forclass�����̲�д�ֻ���
			// for some kinds of pads, such as Zii Pad / Dell Pad, bitmap with "Bitmap.Config.RGB_565" attribute could read every pixel's bytes number correctly;
			// for some pads else, such as Cube Pad, the bitmap will get wrong color;
			// The usage of the "mBitmap": set the "mBitmap" to the "mCanvas", and draw the "mCanvas" with certain paint through clcualted path, then draw
			// the "mBitmap" to the visible canvas.
			/*	mBitmap = Bitmap.createBitmap(iWidth, iHeight, Bitmap.Config.RGB_565);*/
			mBitmap = Bitmap.createBitmap(iWidth, iHeight, Bitmap.Config.ARGB_8888);
			// bypass end
		}
		if (mCanvas == null) {
			mCanvas = new Canvas(mBitmap);
			try{
				Brush.initialize("");
				Brush.initCanvas(mCanvas, m_nMaxPenWidth);
			}catch(Exception e){
				Log.d(TAG, "2:" + e.getMessage());
			}catch(UnsatisfiedLinkError e) {
				e.printStackTrace();
			}
			if (mCanvas != null)
				mCanvas.drawColor(m_iBackColor);
		}
		if (mBitmapPaint == null) {
			mBitmapPaint = new Paint(Paint.DITHER_FLAG);
			mBitmapPaint.setStyle(Paint.Style.FILL);
			mBitmapPaint.setColor( m_iWriteColor );
			mBitmapPaint.setStrokeWidth(m_iWriteWidth);
			mBitmapPaint.setPathEffect(null);
			mBitmapPaint.setAntiAlias(true);
		}
		if (mUpdateStroke) {
			drawBackGround( mBitmapPaint, mCanvas, iWidth, iHeight, true );
			mUpdateStroke = false;
			mBitmapPaint.setStyle(Paint.Style.FILL);
			mBitmapPaint.setColor( m_iWriteColor );
			mBitmapPaint.setStrokeWidth(m_iWriteWidth);
		}
		if (mLinePaint == null) {
			mLinePaint = new Paint(Paint.DITHER_FLAG);
			mLinePaint.setStyle(Paint.Style.STROKE);
			mLinePaint.setColor(m_iWriteColor);
			if (m_bShowStyle)
				mLinePaint.setStrokeWidth(m_nMinPenWidth);
			else
				mLinePaint.setStrokeWidth(m_iWriteWidth);
			mLinePaint.setStrokeJoin(Join.ROUND);
			mLinePaint.setStrokeCap(Cap.ROUND);
			mLinePaint.setAntiAlias(true);
		}
		return true;
	}

	protected void SafeEnableBitmapCache() {
		if (mBitmap != null) {
			int nW = mBitmap.getWidth();
			int nH = mBitmap.getHeight();
			if ((nH < m_iHeight - m_iMargin * 2) || (nW < m_iWidth - m_iMargin * 2))
			{
				mCanvas = null;
				mBitmap.recycle();
				mBitmap = null;
				mLastW = 0;
				mImageWidth = Math.max(nW, m_iWidth - m_iMargin * 2);
				mImageHeight = Math.max(nW, m_iHeight - m_iMargin * 2);
				mUpdateStroke = true;
			}
		}
		if (mUpdateStroke) {
			synchronized(this)
			{
				m_iWidth = getWidth() - mPadding.left - mPadding.right;
				m_iHeight = getHeight() - mPadding.top - mPadding.bottom;
				if (mImageWidth>0 && mImageHeight>0)
					EnableImageCache(mImageWidth, mImageHeight);
				else
					EnableImageCache(m_iWidth, m_iHeight);
			}
		}
	}
	
	protected double CalculateSpeed( int i, long nTick ) {
		if( i == 0 )
		{
			return m_fSlowSpeed;
		}
		
		if( m_vSavePoints.get(i).x < 0 && m_vSavePoints.get(i).y < 0 )
		{
			return -1;
		}
		
		if( m_vSavePoints.get(i-1).x < 0 && m_vSavePoints.get(i-1).y < 0 )
		{
			return -1;
		}
		
		int dt = (int)(nTick - mLastTick);
//		Log.d(TAG, "dt="+dt);
		if( dt == 0 )
			return mLastSpeed;

		int dx = m_vSavePoints.get(i).x - m_vSavePoints.get(i-1).x;
		int dy = m_vSavePoints.get(i).y - m_vSavePoints.get(i-1).y;
		double d = Math.sqrt(dx * dx + dy * dy);
		return d / dt;
	}
	
	protected double CalculateWidthBySpeed( double v ) {
//		Log.d(TAG, "v="+v);
		if( v <= m_fSlowSpeed )
		{
			return m_nMaxPenWidth;
		}
		else if( v <= m_fNormalSpeed )
		{
			double dfRate = (m_nMaxPenWidth - m_nMidPenWidth) / Math.max(m_fNormalSpeed - m_fSlowSpeed, 1.0e-4f);
			return m_nMaxPenWidth - dfRate * (v - m_fSlowSpeed);
		}
		else if( v <= m_fFastSpeed )
		{
			return m_nMidPenWidth;
		}
		else
		{
//			return m_nMidPenWidth * m_fFastSpeed / v;
			double fRatio = v / m_fFastSpeed;
			fRatio = Math.min(fRatio, 2);
			return m_nMidPenWidth - (m_nMidPenWidth - m_nMinPenWidth) * (fRatio - 1);
		}
	}
	
	@Override
	protected void onDraw( Canvas canvas ) {
		super.onDraw( canvas );
		if (canvas == null)
			return;
		Drawable dBack = getBackground();
		if (dBack != null)
			dBack.draw(canvas);//*/
		synchronized(this)
		{
			showCanvas(canvas, mPaint);
		}
	}
	
	protected void showCanvas(Canvas canvas, Paint paint) {
		try{
			if (m_bImageCache) {
				SafeEnableBitmapCache();
				if (mBitmap == null)
					return;
				if (mNeedStartAnimation) {
					mNeedStartAnimation = false;
					startAnimation();
				}
				Rect rect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
				Rect rShow = new Rect(mPadding.left, mPadding.top, m_iWidth+mPadding.left, m_iHeight+mPadding.top);
				//mBitmap.prepareToDraw();
				canvas.drawBitmap(mBitmap, rect, rShow, paint);
				// try to use small image cache
				//if (m_bReadOnly && (rect.right>rShow.width() || rect.bottom>rShow.height()))
				//	SaveSmallImage(rect, rShow);
			} else {
				drawBackGround(paint, canvas, getWidth(), getHeight(), false);
				//ShowAllStrokes(canvas, paint, false);
			}
		}catch(Exception e){
			Log.d(TAG, "onDraw:" + e.getMessage());
		}
	}
	
	protected boolean SaveSmallImage(Rect rect, Rect rectShow) {
		if (mBitmap == null || mCanvas == null)
			return false;
		Paint paint = new Paint();
		paint.setAntiAlias(true);
		Bitmap bmp = Bitmap.createBitmap(rectShow.right - rectShow.left, rectShow.bottom - rectShow.top, Config.ARGB_8888);
		if (bmp == null)
			return false;
		Canvas can = new Canvas(bmp);
		if (can != null)
			can.drawBitmap(mBitmap, rect, rectShow, paint);
		mBitmap = bmp;
		mCanvas = can;
		mImageWidth = 0;
		mImageHeight = 0;
		return true;
	}
	

	
	/*
     *  20130924 added by wang qian for willwrite plugin. 
     * 
     */
	public void setZhuBiMode(boolean zhubimode) {
	    mZhuBiMode = zhubimode;
	}
	
	/*
     *  20130924 added by wang qian for willwrite plugin. 
     * 
     */
	public void continueNextStroke() {
	    if (mFillThread != null && mZhuBiMode) mFillThread.continueFillThread();
	}
	
   /*
     *  20130925 added by wang qian for willwrite plugin. 
     * 
     */
	public boolean getZhuBiMode() {
	    return mZhuBiMode;
	}
	
	//2014-06-16, begin:
	private float m_fdevicedensity = 1.0f;
	private int m_idefault8 = 8;
	private int m_idefault4 = 4;
	
	public void setscreendensity(float fdensity) {
		if(fdensity - 1.0f > 0.1f) {			
			m_fdevicedensity = fdensity;
			m_idefault8 = (int)(m_idefault8 * m_fdevicedensity);
			m_idefault4 = (int)(m_idefault4 * m_fdevicedensity);
		}
	}
	//2014-06-16 end.
}
